package com.xy.iot.netty.connect;

import com.xy.iot.common.enums.DeviceHandleEnum;
import com.xy.iot.common.result.ChannelClientEventData;
import com.xy.iot.common.utils.CacheUtils;
import com.xy.iot.common.utils.IpUtil;
import com.xy.iot.common.utils.SpringContextUtil;
import com.xy.iot.netty.channel.BaseChannel;
import com.xy.iot.netty.device.DeviceClientChannel;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ObjectUtils;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 客户端连接抽象类
 */
@Slf4j
@Sharable
public abstract class BaseClientConnect  extends ChannelInboundHandlerAdapter implements TimerTask {
    /**
     * 重连时间
     */
    private final Integer timeOut;
    /**
     * 启动类
     */
    private final Bootstrap boot;
    /**
     * 定时器
     */
    private final Timer timer;

    /**
     * 重连地址
     */
    private final Map<Timeout, SocketAddress> timeoutAddressMap = new HashMap<>();

    /**
     * 连接成功次数
     */
    private final Map<String, Integer> onLineMap = new HashMap<>();

    /**
     * 连接断开次数
     */
    private final Map<String, Integer> breakOffMap = new HashMap<>();

    /**
     * 重连失败次数
     */
    private final Map<String, Integer> retriesMap = new HashMap<>();

    /**
     * 客户端连接和断开监听
     * @param boot
     * @param reconnectTime
     */
    public BaseClientConnect(Bootstrap boot, int reconnectTime){
        this.boot = boot;
        this.timeOut = reconnectTime;
        this.timer = new HashedWheelTimer();
    }

    /**
     * 连接开始
     * @param socketAddress
     *                  addListener方法：
     *                              允许向ChannelFuture添加一个或多个监听器，这些监听器会在ChannelFuture所代表的异步操作完成后被调用。
     *                  new ChannelFutureListener()：
     *                              这里创建了一个匿名内部类来实现ChannelFutureListener接口。
     *                              你可以自定义操作完成后的处理逻辑。
     *                  operationComplete方法：
     *                              这是ChannelFutureListener接口中的唯一方法，当关联的异步操作完成时会被调用。
     *                              在此方法内，你可以访问ChannelFuture对象来检查操作是否成功，以及获取可能的异常信息。
     * @return
     * @throws Exception
     */
    public ChannelFuture connect(SocketAddress socketAddress) throws Exception {
        ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
        ChannelFuture future = boot.connect(socketAddress);//启动连接,返回一个监视器
        //添加一个监听器，监听连接是否成功
        future.addListener(new ChannelFutureListener() {
            /**
             * 当关联的异步操作完成时会被调用
             * @param future
             * @throws Exception
             */
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    //连接失败，重连服务端，重连交给后端线程执行
                    log.error("连接失败：{}",socketAddress);
                    future.channel().eventLoop().schedule(() -> {
                        int retries = (ObjectUtils.isEmpty(retriesMap.get(socketAddress.toString())) ? 0 : retriesMap.get(socketAddress.toString())) + 1;
                        retriesMap.put(socketAddress.toString(), retries);
                        if(!ObjectUtils.isEmpty(socketAddress)){
                            ChannelClientEventData clientEventData = new ChannelClientEventData(this, DeviceHandleEnum.BREAK_RECONNECT.getKey(), socketAddress.toString(), retries);
                            if(!ObjectUtils.isEmpty(clientEventData) && !ObjectUtils.isEmpty(applicationContext)){
                                applicationContext.publishEvent(clientEventData);
                            }
                        }
                        try {
                            connect(socketAddress);
                        } catch (Exception e) {
                           log.error("重新连接失败：{}",socketAddress,e);
                        }
                    }, timeOut, TimeUnit.MILLISECONDS);
                } else {
                    //服务端连接成功,回调
                    afterSuccess();
                    int onLine = (ObjectUtils.isEmpty(onLineMap.get(socketAddress.toString())) ? 0 : onLineMap.get(socketAddress.toString())) + 1;
                    onLineMap.put(socketAddress.toString(), onLine);
                    if(!ObjectUtils.isEmpty(socketAddress)){
                        ChannelClientEventData clientEventData = new ChannelClientEventData(this, DeviceHandleEnum.ON_LINE.getKey(), socketAddress.toString(), onLine);
                        if(!ObjectUtils.isEmpty(clientEventData) && !ObjectUtils.isEmpty(applicationContext)){
                            applicationContext.publishEvent(clientEventData);
                        }
                    }
                }
            }
        });
        //确保当前线程会等待到与之相关的网络通道完全关闭之后才继续执行。这对于需要确保所有资源正确释放和清理的场景非常有用，尤其是在关闭服务或者清理资源的代码逻辑中。但需要注意的是，由于它会导致线程阻塞，因此应当谨慎使用，特别是在可能影响应用响应性和性能的场合
        future
                .channel()//方法用于从ChannelFuture中获取对应的Channel对象，即网络通信的通道。
                .closeFuture()//方法用于获取ChannelFuture的CloseFuture对象，该对象表示通道关闭的异步操作。当调用Channel的close()方法开始关闭通道后，closeFuture()就会在未来某个时刻完成，表示通道已经成功关闭或关闭过程中发生了错误。
                .sync()//最后调用sync()方法是ChannelFuture的一个阻塞操作。这会使当前线程等待，直到与ChannelFuture相关的操作（在这个上下文中是通道关闭操作）完成。 也就是说，sync()会阻塞调用线程，直到closeFuture所代表的关闭操作完成，无论这个操作成功还是因为某种原因失败了
        ;
        return future;
    }
    /**
     * 运行 定时器任务
     * @param timeout
     * @throws Exception
     */
    @Override
    public void run(Timeout timeout) throws Exception {
        if(!ObjectUtils.isEmpty(timeoutAddressMap.get(timeout))){
            connect(timeoutAddressMap.get(timeout));
        }
    }
    /**
     * 当通道变得活动（即连接已建立）时调用。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //成功后，重连失败次数清零
        Channel channel = ctx.channel();
        ctx.fireChannelActive();
        if(!ObjectUtils.isEmpty(channel.remoteAddress())){
            String address = IpUtil.getAddress(channel.remoteAddress());
            retriesMap.put(address, 0);
            //将柜地址与通讯管道的绑定关系写入缓存
            DeviceClientChannel deviceChannel = new DeviceClientChannel(channel);
            CacheUtils.put(deviceChannel.name(), deviceChannel);
            log.info("设备连接成功：{}",address);
        }
    }
    /**
     * 当通道不再活动（即连接被关闭或中断）时调用。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelInactive();
        Channel channel = ctx.channel();
        ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
        if(!ObjectUtils.isEmpty(channel) && !ObjectUtils.isEmpty(channel.remoteAddress())){
            String address = IpUtil.getAddress(channel.remoteAddress());
            Integer breakOff = (ObjectUtils.isEmpty(breakOffMap.get(address)) ? 0 : breakOffMap.get(address)) + 1;
            breakOffMap.put(address, breakOff);
            ChannelClientEventData clientEventData = new ChannelClientEventData(this, DeviceHandleEnum.BREAK_OFF.getKey(), address, breakOff);
            if(!ObjectUtils.isEmpty(clientEventData) && !ObjectUtils.isEmpty(applicationContext)){
                applicationContext.publishEvent(clientEventData);
            }

            //将通讯管道的绑定关系从缓存中删除
            DeviceClientChannel deviceClientChannel = new DeviceClientChannel(channel);
            BaseChannel isExist = (BaseChannel) CacheUtils.get(deviceClientChannel.name());
            if(!ObjectUtils.isEmpty(isExist)){
                CacheUtils.remove(deviceClientChannel.name());
                log.info("断线重连：{}",address);
                if(address.contains(":")) {
                    String[] strArray = address.split(":");
                    Timeout timeout = timer.newTimeout(this, timeOut, TimeUnit.MILLISECONDS);//创建一个定时任务，在指定的时间后执行
                    InetSocketAddress socketAddress = InetSocketAddress.createUnresolved(strArray[0], Integer.parseInt(strArray[1]));//创建一个InetSocketAddress对象，指定IP地址和端口号
                    timeoutAddressMap.put(timeout, socketAddress);//将定时任务与InetSocketAddress对象绑定
                }
            }
            //连接断开后的最后处理
            ctx.pipeline().remove(ctx.handler());//移除当前ChannelHandler
            ctx.deregister();//移除ChannelHandlerContext
            ctx.close();//关闭Channel
        }
    }

    public abstract void afterSuccess();
}
